﻿namespace etracks.Helpers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    ////////////////////////////////////////////////////////////////////////////////
    // Edge

    public class Edge<T> where T : struct,
        IComparable,
        IComparable<T>,
        IConvertible,
        IEquatable<T>
    {
        public string Label { get; set; }
        public Guid Id { get; set; }
        public T Value { get; set; }

        public Node<T> Node { get; set; }

        public Edge()
        {
        }

        public Edge(Node<T> node, T value)
        {
            this.Id = Guid.NewGuid();
            this.Node = node;
            this.Value = value;
        }
    }

    ////////////////////////////////////////////////////////////////////////////////
    // Node

    public class Node<T> where T : struct, 
          IComparable,
          IComparable<T>,
          IConvertible,
          IEquatable<T>
    {
        ////////////////////////////////////////
        // Delegates
        //public delegate void SampleEventHandler(object sender, SampleEventArgs e);

        ////////////////////////////////////////
        // Events
        // Declare the event. 
        //public event Action<T> BiasChanged;
        //public event Action<Node<T>> ValueChanged;
        //public event Action<Node<T>> EdgeAdded;

        public Action<Node<T>> ComputeValue;

        public Action Recompute;

        private void _Recompute()
        {
            if (ComputeValue != null)
                this.ComputeValue(this);
        }
        

        ////////////////////////////////////////
        // Properties

        public string Label { get; set; }
        public Guid Id { get; set; }

        private T _Value;
        public T Value
        {
            get
            {
                return this._Value;
            }
            set
            {
                this._Value = value;
                foreach (Delegate d in notificationList)
                    d.DynamicInvoke();
            }
        }

        private T _Bias;
        public T Bias 
        { 
            get
            {
                return this._Bias;
            }
            set
            {
                this._Bias = value;
                foreach (Delegate d in notificationList)
                    d.DynamicInvoke();
            }
        }
        public List<Edge<T>> Edges { get; set; }

        #region constructors
        public Node()
            : this(default(T), default(T))
        {
        }

        public Node(T value)
            : this(value, default(T))
        {
        }

        public Node(T value, string label) : this(value, default(T), label)
        {
        }

        public Node(T value, T bias) : this(value, bias, string.Empty)
        {
        }

        public Node(T value, T bias, string label)
        {
            
            this.Id = Guid.NewGuid();
            this.Value = value;
            this.Bias = bias;
            this.Label = label;
            this.Edges = new List<Edge<T>>();
            this.Recompute = this._Recompute;
        }

        #endregion constructors

        ////////////////////////////////////////
        // Methods

        #region HasEdge
        public bool HasEdge(Guid toNodeId)
        {
            return this.Edges.Exists(n => n.Node.Id == toNodeId);
        }

        public bool HasEdge(Node<T> toNode)
        {
            return this.Edges.Exists(n => n.Node.Id == toNode.Id);
        }
        #endregion HasEdge

        #region GetEdge
        public Edge<T> GetEdge(Guid toNodeId)
        {
            return this.Edges.Where(n => n.Node.Id == toNodeId).FirstOrDefault();
        }

        public Edge<T> GetEdge(Node<T> toNode)
        {
            return this.Edges.Where(n => n.Node.Id == toNode.Id).FirstOrDefault();
        }
        #endregion GetEdge

        private List<Delegate> notificationList = new List<Delegate>();

        public void AddEdge(Node<T> toTargetNode, T weight)
        {
            if (!toTargetNode.HasEdge(this))
            {
                toTargetNode.Edges.Add(new Edge<T>(this, weight));
                this.notificationList.Add(toTargetNode.Recompute);
            }

            if (toTargetNode.ComputeValue != null)
                toTargetNode.ComputeValue(toTargetNode);

            #region comment out
            //if (!this.HasEdge(toTargetNode))
            //{
            //    this.Edges.Add(new Edge<T>(toTargetNode, weight));
            //}
            //if (ComputeValue != null)
            //    ComputeValue(this);
            #endregion comment out
        }

        #region comment out
        //public void SetValue(T value)
        //{
        //    this.Value = value;

        //    //if (this.ValueChanged != null)
        //    //    this.ValueChanged(this);

        //    foreach (Delegate d in dlist)
        //    {
        //        d.DynamicInvoke(null);
        //    }

        //}
        #endregion comment out
    }

    public class Graph
    {
        public List<Node<double>> Nodes { get; set; }

        public Graph()
        {
            this.Nodes = new List<Node<double>>();
        }

        public static void Sigmoid(Node<double> node)
        {
            node.Value = 1.0d / (1.0d + Math.Pow(Math.E, -(node.Edges.Sum(n => n.Value * n.Node.Value) + node.Bias)));
        }

        public static void HyperTan(Node<double> node)
        {
            node.Value = Math.Tanh(node.Edges.Sum(n => n.Value * n.Node.Value) + node.Bias);
        }

        public static void test()
        {
            Graph g = new Graph();

            Node<double> in0 = new Node<double>(1.0d);
            Node<double> in1 = new Node<double>(2.0d);
            Node<double> in2 = new Node<double>(3.0d);

            Node<double> hn0 = new Node<double>();
            Node<double> hn1 = new Node<double>();
            Node<double> hn2 = new Node<double>();
            Node<double> hn3 = new Node<double>();

            Node<double> on0 = new Node<double>();
            Node<double> on1 = new Node<double>();

            in0.AddEdge(hn0, 0.1d);
            in0.AddEdge(hn1, 0.2d);
            in0.AddEdge(hn2, 0.3d);
            in0.AddEdge(hn3, 0.4d);

            in1.AddEdge(hn0, 0.5d);
            in1.AddEdge(hn1, 0.6d);
            in1.AddEdge(hn2, 0.7d);
            in1.AddEdge(hn3, 0.8d);

            in2.AddEdge(hn0, 0.9d);
            in2.AddEdge(hn1, 1.0d);
            in2.AddEdge(hn2, 1.1d);
            in2.AddEdge(hn3, 1.2d);


            //in0.Edges.Add(new Edge<double>(hn0, 0.1d)); //
            //in0.Edges.Add(new Edge<double>(hn1, 0.2d)); 
            //in0.Edges.Add(new Edge<double>(hn2, 0.3d));
            //in0.Edges.Add(new Edge<double>(hn3, 0.4d));

            //in1.Edges.Add(new Edge<double>(hn0, 0.5d)); //
            //in1.Edges.Add(new Edge<double>(hn1, 0.6d));
            //in1.Edges.Add(new Edge<double>(hn2, 0.7d));
            //in1.Edges.Add(new Edge<double>(hn3, 0.8d));

            //in2.Edges.Add(new Edge<double>(hn0, 0.9d)); //
            //in2.Edges.Add(new Edge<double>(hn1, 1.0d));
            //in2.Edges.Add(new Edge<double>(hn2, 1.1d));
            //in2.Edges.Add(new Edge<double>(hn3, 1.2d));

            // Calculate sum

            Console.ReadLine();
        }
    }

}
